package com.oner365.monitor.service.impl;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

import org.quartz.JobDataMap;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.alibaba.fastjson.JSON;
import com.oner365.data.commons.exception.ProjectRuntimeException;
import com.oner365.data.commons.util.DataUtils;
import com.oner365.data.jpa.page.PageInfo;
import com.oner365.data.jpa.query.QueryCriteriaBean;
import com.oner365.data.jpa.query.QueryUtils;
import com.oner365.monitor.constants.ScheduleConstants;
import com.oner365.monitor.dao.ISysTaskDao;
import com.oner365.monitor.dto.SysTaskDto;
import com.oner365.monitor.entity.SysTask;
import com.oner365.monitor.enums.TaskStatusEnum;
import com.oner365.monitor.exception.TaskException;
import com.oner365.monitor.service.ISysTaskService;
import com.oner365.monitor.util.CronUtils;
import com.oner365.monitor.util.ScheduleUtils;
import com.oner365.monitor.vo.SysTaskVo;
import com.oner365.queue.service.IQueueSendService;

/**
 * 定时任务调度信息 服务层
 *
 * @author liutao
 */
@Service
public class SysTaskServiceImpl implements ISysTaskService {

    private static final Logger LOGGER = LoggerFactory.getLogger(SysTaskServiceImpl.class);

    @Resource
    private Scheduler scheduler;

    @Resource
    private ISysTaskDao dao;

    @Resource
    private IQueueSendService queueSendService;

    /**
     * 项目启动时，初始化定时器 主要是防止手动修改数据库导致未同步到定时任务处理（注：不能手动修改数据库ID和任务组名，否则会导致脏数据）
     */
    @PostConstruct
    public void init() throws SchedulerException {
        scheduler.clear();
        List<SysTask> taskList = dao.findAll();
        taskList.forEach(task -> {
            try {
                ScheduleUtils.createScheduleJob(scheduler, convert(task, SysTaskDto.class));
            }
            catch (Exception e) {
                LOGGER.error("Error init:", e);
            }
        });
    }

    @Override
    public PageInfo<SysTaskDto> pageList(QueryCriteriaBean data) {
        try {
            Page<SysTask> page = dao.findAll(QueryUtils.buildCriteria(data), QueryUtils.buildPageRequest(data));
            return convert(page, SysTaskDto.class);
        }
        catch (Exception e) {
            LOGGER.error("Error pageList: ", e);
        }
        return null;
    }

    @Override
    public List<SysTaskDto> findList(QueryCriteriaBean data) {
        try {
            if (data.getOrder() == null) {
                return convert(dao.findAll(QueryUtils.buildCriteria(data)), SysTaskDto.class);
            }
            List<SysTask> list = dao.findAll(QueryUtils.buildCriteria(data),
                    Objects.requireNonNull(QueryUtils.buildSortRequest(data.getOrder())));
            return convert(list, SysTaskDto.class);
        }
        catch (Exception e) {
            LOGGER.error("Error findList: ", e);
        }
        return Collections.emptyList();
    }

    @Override
    public SysTaskDto selectTaskById(String id) {
        Optional<SysTask> optional = dao.findById(id);
        return convert(optional.orElse(null), SysTaskDto.class);
    }

    @Override
    @Transactional(rollbackFor = ProjectRuntimeException.class)
    public Boolean pauseTask(SysTaskVo vo) throws SchedulerException {
        Optional<SysTask> optional = dao.findById(vo.getId());
        if (optional.isPresent()) {
            SysTask sysTask = optional.get();
            sysTask.setStatus(TaskStatusEnum.PAUSE);
            dao.save(sysTask);
            scheduler.pauseJob(ScheduleUtils.getJobKey(sysTask.getId(), sysTask.getTaskGroup()));
            queueSendService.saveExecuteTaskLog(convert(sysTask, SysTaskDto.class));
            return Boolean.TRUE;
        }
        return Boolean.FALSE;
    }

    @Override
    @Transactional(rollbackFor = ProjectRuntimeException.class)
    public Boolean resumeTask(SysTaskVo vo) throws SchedulerException {
        Optional<SysTask> optional = dao.findById(vo.getId());
        if (optional.isPresent()) {
            SysTask sysTask = optional.get();
            sysTask.setStatus(TaskStatusEnum.NORMAL);
            dao.save(sysTask);
            scheduler.resumeJob(ScheduleUtils.getJobKey(sysTask.getId(), sysTask.getTaskGroup()));
            queueSendService.saveExecuteTaskLog(convert(sysTask, SysTaskDto.class));
            return Boolean.TRUE;
        }
        return Boolean.FALSE;
    }

    @Override
    @Transactional(rollbackFor = ProjectRuntimeException.class)
    public Boolean deleteTask(String id) {
        try {
            Optional<SysTask> optional = dao.findById(id);
            if (optional.isPresent()) {
                SysTask task = optional.get();
                String taskGroup = task.getTaskGroup();
                dao.deleteById(id);
                scheduler.deleteJob(ScheduleUtils.getJobKey(id, taskGroup));
                return Boolean.TRUE;
            }
        }
        catch (SchedulerException e) {
            LOGGER.error("deleteTask error", e);
        }
        return Boolean.FALSE;
    }

    @Override
    @Transactional(rollbackFor = ProjectRuntimeException.class)
    public List<Boolean> deleteTaskByIds(String[] ids) {
        return Arrays.stream(ids).map(this::deleteTask).collect(Collectors.toList());
    }

    @Override
    @Transactional(rollbackFor = ProjectRuntimeException.class)
    public Boolean changeStatus(SysTaskVo task) throws SchedulerException {
        Boolean rows = Boolean.FALSE;
        if (TaskStatusEnum.NORMAL.equals(task.getStatus())) {
            rows = resumeTask(task);
        }
        else if (TaskStatusEnum.PAUSE.equals(task.getStatus())) {
            rows = pauseTask(task);
        }
        return rows;
    }

    @Override
    @Transactional(rollbackFor = ProjectRuntimeException.class)
    public Boolean run(SysTaskVo task) throws SchedulerException {
        String id = task.getId();
        String taskGroup = task.getTaskGroup();
        Optional<SysTask> optional = dao.findById(task.getId());
        if (optional.isPresent()) {
            // 参数
            SysTask sysTask = optional.get();
            sysTask.setTaskGroup(taskGroup);
            JobDataMap dataMap = new JobDataMap();
            dataMap.put(ScheduleConstants.TASK_PROPERTIES, JSON.toJSONString(convert(sysTask, SysTaskDto.class)));
            scheduler.triggerJob(ScheduleUtils.getJobKey(id, taskGroup), dataMap);
            queueSendService.saveExecuteTaskLog(convert(sysTask, SysTaskDto.class));
            return Boolean.TRUE;
        }
        return Boolean.FALSE;
    }

    @Override
    @Transactional(rollbackFor = ProjectRuntimeException.class)
    public Boolean save(SysTaskVo task) throws SchedulerException, TaskException {
        boolean isAdd = DataUtils.isEmpty(task.getId());
        if (isAdd && DataUtils.isEmpty(task.getStatus())) {
            task.setStatus(TaskStatusEnum.PAUSE);
        }
        SysTask entity = dao.save(convert(task, SysTask.class));
        if (isAdd) {
            ScheduleUtils.createScheduleJob(scheduler, convert(entity, SysTaskDto.class));
            queueSendService.saveExecuteTaskLog(convert(entity, SysTaskDto.class));
        }
        return Boolean.TRUE;
    }

    @Override
    @Transactional(rollbackFor = ProjectRuntimeException.class)
    public Boolean updateTask(SysTaskVo task) throws SchedulerException, TaskException {
        Optional<SysTask> optional = dao.findById(task.getId());
        if (optional.isPresent()) {
            SysTask entity = optional.get();
            save(task);
            updateSchedulerTask(task, entity.getTaskGroup());
            return Boolean.TRUE;
        }
        return Boolean.FALSE;
    }

    private void updateSchedulerTask(SysTaskVo task, String taskGroup) throws SchedulerException, TaskException {
        String taskId = task.getId();
        // 判断是否存在
        JobKey taskKey = ScheduleUtils.getJobKey(taskId, taskGroup);
        if (scheduler.checkExists(taskKey)) {
            // 防止创建时存在数据问题 先移除，然后在执行创建操作
            scheduler.deleteJob(taskKey);
        }
        ScheduleUtils.createScheduleJob(scheduler, convert(task, SysTaskDto.class));
        queueSendService.saveExecuteTaskLog(convert(task, SysTaskDto.class));
    }

    @Override
    public Boolean checkCronExpressionIsValid(String cronExpression) {
        return CronUtils.isValid(cronExpression);
    }

}
